/*
* @file gkt_date_time.h
*/

#ifndef GKT_DATE_TIME_H
#define GKT_DATE_TIME_H

#include "gkt_types.h"

#ifdef __cplusplus
extern "C" {
#endif

#define GKT_DT_MONTH_JAN	1
#define GKT_DT_MONTH_FEB	2
#define GKT_DT_MONTH_MAR	3
#define GKT_DT_MONTH_APR	4
#define GKT_DT_MONTH_MAY	5
#define GKT_DT_MONTH_JUN	6
#define GKT_DT_MONTH_JUL	7
#define GKT_DT_MONTH_AUG	8
#define GKT_DT_MONTH_SEP	9
#define GKT_DT_MONTH_OCT	10
#define GKT_DT_MONTH_NOV	11
#define GKT_DT_MONTH_DEC	12

#define GKT_DT_WEEK_MON	1
#define GKT_DT_WEEK_TUE	2
#define GKT_DT_WEEK_WED	3
#define GKT_DT_WEEK_THU	4
#define GKT_DT_WEEK_FRI	5
#define GKT_DT_WEEK_SAT	6
#define GKT_DT_WEEK_SUN	7

#ifdef GKT_CONFIG_DT_YEAR_BASE
#define GKT_DT_YEAR_BASE	GKT_CONFIG_DT_YEAR_BASE
#define GKT_DT_WEEK_BASE	GKT_CONFIG_DT_WEEK_BASE
#else
#define GKT_DT_YEAR_BASE	2000
#define GKT_DT_WEEK_BASE	GKT_DT_WEEK_SAT
#endif

#define GKT_DT_MINUTE_SECONDS	60
#define GKT_DT_HOUR_MINUTES	60
#define GKT_DT_HOUR_SECONDS	\
	(GKT_DT_HOUR_MINUTES * GKT_DT_MINUTE_SECONDS)

#define GKT_DT_DAY_HOURS	24
#define GKT_DT_DAY_MINUTES	\
	(GKT_DT_DAY_HOURS * GKT_DT_HOUR_MINUTES)
#define GKT_DT_DAY_SECONDS	\
	(GKT_DT_DAY_HOURS * GKT_DT_HOUR_SECONDS)

#define GKT_DT_YEAR_MONTHS	12
#define GKT_DT_MONTH_MAX_DAYS	31
#define GKT_DT_MONTH_MIN_DAYS	28
#define GKT_DT_WEEK_DAYS	7

#define GKT_DT_NORMAL_YEAR_DAYS	365
#define GKT_DT_NORMAL_YEAR_SECONDS	\
	(GKT_DT_NORMAL_YEAR_DAYS * GKT_DT_DAY_SECONDS)

#define GKT_DT_LEAP_YEAR_DAYS	366
#define GKT_DT_LEAP_YEAR_SECONDS	\
	(GKT_DT_LEAP_YEAR_DAYS * GKT_DT_DAY_SECONDS)

typedef union _gkt_date {
	struct {
		uint8_t	day;	/* 1 ~ 28/29/30/31 */
		uint8_t	month;	/* GKT_DT_MONTH_JAN(1) ~ GKT_DT_MONTH_DEC(12) */
		uint16_t	year;	/* after GKT_DT_YEAR_BASE */
	};
	uint32_t	u32_value;
} gkt_date_u;

#define GKT_DT_DATE(_year, _month, _day)	\
	{	\
		.year	= (uint16_t)(_year),	\
		.month	= (uint8_t)(_month),	\
		.day	= (uint8_t)(_day),	\
	}

typedef union _gkt_time {
	struct {
		uint8_t	second;	/* 0 ~ 59 */
		uint8_t	minute;	/* 0 ~ 59 */
		uint8_t	hour;	/* 0 ~ 23 */
		uint8_t	reserved;
	};
	uint32_t	u32_value;
} gkt_time_u;

#define GKT_DT_TIME(_hour, _minute, _second)	\
	{	\
		.hour	= (uint8_t)(_hour),	\
		.minute	= (uint8_t)(_minute),	\
		.second	= (uint8_t)(_second),	\
		.reserved	= 0,	\
	}

/* date & time struct type */
typedef struct _gkt_date_time {
	gkt_date_u	date;
	gkt_time_u	time;
} gkt_date_time_s, gkt_dts_s;

#define GKT_DATE_TIME(_year, _month, _day, _hour, _minute, _second)	\
	{	\
		.date	= GKT_DT_DATE(_year, _month, _day),	\
		.time	= GKT_DT_TIME(_hour, _minute, _second),	\
	}

/* date & time unsigned integer type */
typedef uint32_t	gkt_dtu_t;

#define GKT_DTU_SECOND_BITS	6
#define GKT_DTU_MINUTE_BITS	6
#define GKT_DTU_HOUR_BITS	5
#define GKT_DTU_DAY_BITS	5
#define GKT_DTU_MONTH_BITS	4
#define GKT_DTU_YEAR_BITS	6

#define GKT_DTU_SECOND_SHIFT	0
#define GKT_DTU_MINUTE_SHIFT	(GKT_DTU_SECOND_SHIFT + GKT_DTU_SECOND_BITS)
#define GKT_DTU_HOUR_SHIFT	(GKT_DTU_MINUTE_SHIFT + GKT_DTU_MINUTE_BITS)
#define GKT_DTU_DAY_SHIFT	(GKT_DTU_HOUR_SHIFT + GKT_DTU_HOUR_BITS)
#define GKT_DTU_MONTH_SHIFT	(GKT_DTU_DAY_SHIFT + GKT_DTU_DAY_BITS)
#define GKT_DTU_YEAR_SHIFT	(GKT_DTU_MONTH_SHIFT + GKT_DTU_MONTH_BITS)

#define GKT_DTU_ITEM_VALUE(dtu, ITEM)	\
	(((dtu)>> GKT_DTU_##ITEM##_SHIFT) & ((1U << GKT_DTU_##ITEM##_BITS) - 1))

/* date_time struct to data time unsigned integer */
#define GKT_DTS_2_DTU(dts)	\
	((((uint32_t)(dts).date.year - GKT_DT_YEAR_BASE) << GKT_DTU_YEAR_SHIFT) \
		| ((uint32_t)(dts).date.month << GKT_DTU_MONTH_SHIFT) \
		| ((uint32_t)(dts).date.day << GKT_DTU_DAY_SHIFT) \
		| ((uint32_t)(dts).time.hour << GKT_DTU_HOUR_SHIFT) \
		| ((uint32_t)(dts).time.minute << GKT_DTU_MINUTE_SHIFT) \
		| ((uint32_t)(dts).time.second << GKT_DTU_SECOND_SHIFT))
#define GKT_DTU_2_DTS(dts, dtu)	\
	do {	\
		(dts).date.year = (uint16_t)GKT_DTU_ITEM_VALUE(dtu, YEAR) + GKT_DT_YEAR_BASE;	\
		(dts).date.month = (uint8_t)GKT_DTU_ITEM_VALUE(dtu, MONTH);	\
		(dts).date.day = (uint8_t)GKT_DTU_ITEM_VALUE(dtu, DAY);	\
		(dts).time.hour = (uint8_t)GKT_DTU_ITEM_VALUE(dtu, HOUR);	\
		(dts).time.minute = (uint8_t)GKT_DTU_ITEM_VALUE(dtu, MINUTE);	\
		(dts).time.second = (uint8_t)GKT_DTU_ITEM_VALUE(dtu, SECOND);	\
		(dts).time.reserved = 0;	\
	} while (0)

__STATIC_INLINE void gkt_dt_evaluate(gkt_date_time_s *dt_to, gkt_date_time_s *dt_from)
{
	dt_to->date.u32_value = dt_from->date.u32_value;
	dt_to->time.u32_value = dt_from->time.u32_value;
}

__STATIC_INLINE int gkt_dt_leap_year(uint16_t year)
{
	return (!(year & 0x03) && ((year % 100) || !(year % 400))) ? 1 : 0;
}

/*
 * @brief check date validaty
 */
int gkt_dt_check_date(const gkt_date_u *date);

/*
 * @brief check time validaty
 */
__STATIC_INLINE int gkt_dt_check_time(const gkt_time_u *time)
{
	return ((time->hour < GKT_DT_DAY_HOURS)
			&& (time->minute < GKT_DT_HOUR_MINUTES)
			&& (time->second < GKT_DT_MINUTE_SECONDS)) ? 1 : 0;
}

void gkt_dt_add_seconds(gkt_date_time_s *dt, uint32_t seconds);
void gkt_dt_add_days(gkt_date_u *date, uint32_t days);

int gkt_dt_days_elapsed_from_base(const gkt_date_u *date, 
					const gkt_date_u *date_base);

int gkt_dt_seconds_elapsed_from_base(
				const gkt_date_time_s *dt, 
				const gkt_date_time_s *dt_base,
				uint32_t *seconds_elapsed);

uint32_t gkt_dt_get_week(const gkt_date_u *date);

int gkt_dt_localtime(gkt_date_time_s *dt, uint32_t seconds);

#ifdef __cplusplus
}
#endif

#endif	/* GKT_DATE_TIME_H */


